Skip to content

Declare property defaults so unset optional properties have a value - #260

Merged
Reshrahim merged 2 commits into
mainfrom
feat/declare-property-defaults
Jul 31, 2026
Merged

Declare property defaults so unset optional properties have a value#260
Reshrahim merged 2 commits into
mainfrom
feat/declare-property-defaults

Conversation

@AzureMike

@AzureMike AzureMike commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Several resource types promise a default in prose without declaring one, and size is the case in radius-project/radius#12532 — "Defaults to S if not provided" is English in a description: field, and nothing reads it. This declares the real thing: size on redisCaches and postgreSqlDatabases, version and database on mySqlDatabases, and database on postgreSqlDatabases.

neo4jDatabases is left alone, since its documented default is the resource name and a static default: can't express that.

Reason for change

Deploying one of these resources without setting the optional property puts a literal {{context.resource.properties.size}} expression into the ARM request, because there's nothing for the recipe to substitute. Declaring the default is half the fix. The other half is radius-project/radius#12563, which makes a declared default: actually get applied — until that ships, nothing here changes behavior, which is why the two are separate and this one goes first.

mySqlDatabases version defaults to 8.4, which is what the AWS Terraform recipe (Data/mySqlDatabases/recipes/aws/terraform/main.tf:31) and the Kubernetes Bicep recipe already fall back to when version is unset. Declaring 8.0 instead would have downgraded both, and a running RDS instance rejects a major version downgrade outright.

Related to radius-project/radius#12532

How to test

Each declared default matches what the recipes already do when the property is unset, so the intended result is no change for anyone who was relying on the existing fallback:

Type Property Declared Matches
redisCaches size S the size == "S" branch of the Azure pack, which the description already documented
postgreSqlDatabases size S same
postgreSqlDatabases database postgres_db the value its description already documented
mySqlDatabases database mysql_db the value its description already documented
mySqlDatabases version 8.4 try(var.version, "8.4") in the AWS recipe and ?? '8.4' in the Kubernetes recipe

With radius-project/radius#12563 applied, an unset size resolves to Balanced_B0 on Redis and Standard_B1ms / Burstable on PostgreSQL, and an unset database resolves to postgres_db and mysql_db.

File change summary

File Summary of change
Data/redisCaches/redisCaches.yaml Declare default: 'S' on size.
Data/postgreSqlDatabases/postgreSqlDatabases.yaml Declare default: 'S' on size and default: postgres_db on database.
Data/mySqlDatabases/mySqlDatabases.yaml Declare default: mysql_db on database and default: '8.4' on version, and reword the version description from "Assumed to be 8.4" to match the other optional properties.

Several resource types documented a default in prose without declaring it, and
the ones that did declare a default had no effect because Radius never
materialized them. Declare the missing keys so they take effect alongside
radius-project/radius schema defaulting.

Added: size on redisCaches and postgreSqlDatabases, version and database on
mySqlDatabases, and database on postgreSqlDatabases. neo4jDatabases is left
alone because its documented default is the resource name, which a static
default cannot express.

The mySqlDatabases version default is 8.4, which is what the AWS Terraform and
Kubernetes Bicep recipes already fall back to when version is unset. The Azure
pack maps everything except 5.7 to 8.0.21, so it deploys the same image either
way and only AWS and Kubernetes would notice a different value. Declaring 8.0
here would have silently downgraded them, and a running RDS instance rejects a
major version downgrade outright.

Related to radius-project/radius#12532

Signed-off-by: Mike Azure <127820851+AzureMike@users.noreply.github.com>
@AzureMike
AzureMike requested review from a team as code owners July 28, 2026 03:08
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Signed-off-by: Mike Azure <127820851+AzureMike@users.noreply.github.com>
@Reshrahim
Reshrahim added this pull request to the merge queue Jul 31, 2026
Merged via the queue into main with commit 2383915 Jul 31, 2026
13 checks passed
@Reshrahim
Reshrahim deleted the feat/declare-property-defaults branch July 31, 2026 22:29
pull Bot pushed a commit to TheTechOddBug/radius that referenced this pull request Aug 1, 2026
…dius-project#12563)

## Summary

A resource type schema can declare a `default` for a property, and
Radius never applied it. `schema.ApplyDefaults` fills absent properties
from the default declared in the schema, and a new `UpdateFilter` on the
dynamic resource PUT path runs it before the resource is saved.

Explicit values are never overwritten, and required properties are never
defaulted, so leaving one out still fails validation instead of being
quietly satisfied.

## Reason for change

Fixes radius-project#12532

Deploying a resource without setting an optional property puts a literal
template expression into the ARM request. The Azure recipe pack writes
parameters as expressions over the resource:

```
skuName: '{{context.resource.properties.size == "S" ? "Balanced_B0" : "Balanced_B1"}}'
```

Leave `size` unset and there's nothing to substitute, so the raw
`{{...}}` string is handed to ARM as the SKU name and rejected against
`allowedValues`.

The cause sits a level below the recipe. Validation checked a resource
against its schema without filling anything in, so every `default:` key
in every resource type has been inert — including the ones
`objectStorage`, `mongoDatabases`, `models`, `kafka` and `rabbitMQ`
already declare.

With the defaults from radius-project/resource-types-contrib#260
declared, the existing recipe expressions resolve on their own, and
neither the resolver nor the recipe pack needs to change. That covers
ten optional properties across the Azure pack, not just `size`:

| Type | Property | Unset today | With defaults |
| --- | --- | --- | --- |
| `redisCaches` | `size` | leaks | `Balanced_B0` |
| `postgreSqlDatabases` | `size` | leaks | `Standard_B1ms` / `Burstable`
|
| `mySqlDatabases` | `version` | leaks | `8.0.21` |
| `models` | `model` | leaks | `gpt-5-mini` |
| `mongoDatabases` | `database` | leaks | `mongo_db` |
| `mySqlDatabases` | `database` | leaks | `mysql_db` |
| `postgreSqlDatabases` | `database` | leaks | `postgres_db` |
| `rabbitMQ` | `queue` | leaks | `jobs` |
| `kafka` | `topic` | leaks | `events` |
| `objectStorage` | `containerName` | leaks | `data` |

Six of those defaults already existed, so half the fix is Radius finally
reading what contrib already wrote down.

Two things reviewers should know. The filter sits ahead of the
encryption filter, so a defaulted sensitive field is still encrypted.
And defaults are reapplied on every write rather than carried forward
from the previous version of the resource, which is what full-replace
PUT semantics ask for — the consequence is that changing a declared
default later changes what an existing resource gets on its next
deployment, so a default is part of a resource type's API contract.

## How to test

`go test ./pkg/schema/... ./pkg/dynamicrp/...` covers the new code
directly. `pkg/schema/defaults_test.go` asserts that explicit values
survive, required properties are never defaulted, read-only properties
are skipped, absent nested objects aren't created, and map and slice
defaults are deep copied so one resource can't mutate what the next one
sees.

End to end, all ten optional properties above were run through
`ApplyDefaults` and the parameter resolver against the real contrib
schemas and the real `recipepack/azure/aks-recipepack.bicep`
expressions. Every one emits a literal `{{...}}` without defaults and
resolves to the value in the table with them, on the unmodified resolver
and the unmodified recipe expressions.

`go vet` and `gofmt` are clean, and `pkg/schema/...`,
`pkg/dynamicrp/...`, `pkg/recipes/...`, `pkg/cli/manifest/...` and
`pkg/ucp/integrationtests/resourceproviders/...` all pass, including
after rebasing onto the kin-openapi 0.144.0 bump in radius-project#12561.

Built-in resource types pick this up once
radius-project/resource-types-contrib#260 merges and `make
update-resource-types` bumps the pin in `go.mod`, since the manifests
under `deploy/manifest/built-in-providers` are copies CI regenerates
from it.

## File change summary

| File | Summary of change |
| ---- | ----------------- |
| `pkg/schema/defaults.go` | New. `ApplyDefaults` walks a schema and
fills absent properties from their declared `default`, returning the
number applied. |
| `pkg/schema/defaults_test.go` | New. Covers explicit values, required
properties, read-only properties, nested objects, and deep copying of
map and slice defaults. |
| `pkg/dynamicrp/frontend/defaultsfilter.go` | New. `UpdateFilter` that
fetches the resource type schema and applies defaults before the
resource is saved, attaching the filled map only when something was
applied. |
| `pkg/dynamicrp/frontend/routes.go` | Register the defaults filter
ahead of the encryption filter. |

---------

Signed-off-by: Mike Azure <127820851+AzureMike@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants